/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.icon.model;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.swing.ImageIcon;
import offset.nodes.Constants;
import offset.nodes.client.model.NodeTypeIcon;
import offset.nodes.client.model.PropertyTypeIcon;
import offset.nodes.server.binary.controller.BinaryAttribute;
import offset.nodes.server.model.RepositoryModel;
import offset.nodes.server.model.ServerNamespaceRegistry;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.nodetype.NodeTypeDef;
import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
import org.apache.jackrabbit.core.nodetype.PropDef;
import org.apache.jackrabbit.spi.Name;

/**
 *
 * @author Walter L�tgenau
 */
public class IconModel extends RepositoryModel {

    public IconModel(Session session) {
        super(session);
    }

    /**
     * Return the icon for the property.
     *
     * @param nodeTypeName
     * @param propertyType
     * @param propertyName
     * @param propertyValue
     * @return
     * @throws RepositoryException
     */
    public BinaryAttribute getIcon(String nodeTypeName, int propertyType, String propertyName, String propertyValue) throws RepositoryException {
        if (propertyType >= 0) {
            if (propertyType == PropertyType.BOOLEAN && propertyValue != null)
                return getBooleanValueIcon(propertyValue);
            return getPropertyTypeIcon(propertyType);
        }

        if (nodeTypeName == null)
            return null;

        // if no property provided, return node type icon
        if (propertyName == null)
            return getNodeTypeIcon(nodeTypeName);

        StringBuffer propertyPath = new StringBuffer(getIconDir());
        propertyPath.append(Constants.PATH_SEPARATOR + nodeTypeName);
        propertyPath.append(Constants.PATH_SEPARATOR + propertyName);

        Node root = getSession().getRootNode();
        // if there is no configuration for the specific property, return the property type icon
        if (!root.hasNode(propertyPath.toString()))
            return getPropertyTypeIcon(nodeTypeName, propertyName);

        Node propertyNode = root.getNode(propertyPath.toString());
        // if there is an icon for the property name, return it
        if (propertyNode.hasNode(Constants.CHILD_ICON))
            return getDownloadInfo(propertyNode.getNode(Constants.CHILD_ICON));

        // if there is an icon for the property value, return it
        if (!isValidNodeName(propertyValue)) // e.g. the template editor will use invalid property values
            return null;
        
        if (propertyNode.hasNode(propertyValue))
            return getDownloadInfo(propertyNode.getNode(propertyValue));

        // if there is an icon for the default property value, return it
        if (propertyNode.hasNode(Constants.CHILD_DEFAULT_PROPERTY_VALUE))
            return getDownloadInfo(propertyNode.getNode(propertyValue));

        return null;
    }

    /**
     * Is this a valid node name?
     * 
     * @param name
     * @return
     */
    protected boolean isValidNodeName(String name) {
        return name.matches("[a-zA-Z:0-9]*");
    }

    /**
     * Check, if there is a property value icon.
     *
     * @param nodeTypeName
     * @param propertyName
     * @param propertyValue
     * @return
     * @throws RepositoryException
     */
    public boolean hasPropertyValueIcon(String nodeTypeName, String propertyName, String propertyValue) throws RepositoryException {
        if (nodeTypeName == null || propertyName == null || propertyValue == null)
            return false;

        StringBuffer propertyPath = new StringBuffer(getIconDir());
        propertyPath.append(Constants.PATH_SEPARATOR + nodeTypeName);
        propertyPath.append(Constants.PATH_SEPARATOR + propertyName);
        propertyPath.append(Constants.PATH_SEPARATOR + propertyValue);

        Node root = getSession().getRootNode();

        return root.hasNode(propertyPath.toString());
    }

    /**
     * Check, if there is a property value icon.
     *
     * @param nodeTypeName
     * @param propertyName
     * @param propertyValue
     * @return
     * @throws RepositoryException
     */
    public boolean hasBooleanValueIcons() throws RepositoryException {
        StringBuffer propertyPath = new StringBuffer(getIconDir());
        propertyPath.append(Constants.PATH_SEPARATOR + "BOOLEAN");

        Node root = getSession().getRootNode();

        return root.hasNode(propertyPath.toString() + Constants.PATH_SEPARATOR + Boolean.TRUE.toString()) && root.hasNode(propertyPath.toString() + Constants.PATH_SEPARATOR + Boolean.FALSE.toString());
    }

    /**
     * Return the node type icon. If there is no specific icon for the particular node type,
     * return the default icon.
     *
     * @param nodeTypeName
     * @return the icon, as a DownloadInfo
     * @throws RepositoryException
     */
    public BinaryAttribute getNodeTypeIcon(String nodeTypeName) throws RepositoryException {
        Node root = getSession().getRootNode();
        Node icon = null;
        String nodeTypePath = getIconDir() + Constants.PATH_SEPARATOR + nodeTypeName;
        if (!root.hasNode(nodeTypePath))
            nodeTypePath = getIconDir() + Constants.PATH_SEPARATOR + Constants.CHILD_DEFAULT_NODE_TYPE;
        if (!root.hasNode(nodeTypePath))
            return null;

        icon = root.getNode(nodeTypePath);
        if (icon.isNodeType(Constants.JCR_UNSTRUCTURED)) {
            if (!icon.hasNode(Constants.CHILD_ICON)) {
                nodeTypePath = getIconDir() + Constants.PATH_SEPARATOR + Constants.CHILD_DEFAULT_NODE_TYPE;
                if (!root.hasNode(nodeTypePath))
                    return null;

                icon = root.getNode(nodeTypePath);
            }
            else
                icon = icon.getNode(Constants.CHILD_ICON);
        }
        return getDownloadInfo(icon);
    }
    
    /**
     * Return a list of node type names and associated icons.
     * 
     * @return the list
     * @throws RepositoryException
     * @throws IOException 
     */
    public List<NodeTypeIcon> getNodeTypeIcons() throws RepositoryException, IOException {
        List<NodeTypeIcon> result = new ArrayList<NodeTypeIcon>();

        Node root = getSession().getRootNode();
        Node iconDir = root.getNode(getIconDir());
        
        NodeIterator icons = iconDir.getNodes();
        
        while(icons.hasNext()) {
            Node icon = icons.nextNode();
            
            if (icon.getName().indexOf(Constants.NAMESPACE_DELIMITER) < 0
                    && !icon.getName().equals(Constants.CHILD_DEFAULT_NODE_TYPE)) // property icon
                continue;
            
            String nodeTypeName = icon.getName();
            
            if (icon.isNodeType(Constants.JCR_UNSTRUCTURED)) {
                if (!icon.hasNode(Constants.CHILD_ICON))
                    continue;
                
                icon = icon.getNode(Constants.CHILD_ICON);
            }
            
            if (!icon.isNodeType(Constants.TYPE_NODES_FILE))
                continue;
            
            byte[] imageIcon = getImageIcon(icon);
            if (imageIcon == null)
                continue;
            
            result.add(new NodeTypeIcon(nodeTypeName, imageIcon));
        }
        
        return result;    
    }
    
    /**
     * Return the list of property icons for all property types.
     * 
     * @return the list of property icons
     * @throws RepositoryException
     * @throws IOException 
     */
    public List<PropertyTypeIcon> getPropertyTypeIcons() throws RepositoryException, IOException {
        List<PropertyTypeIcon> result = new ArrayList<PropertyTypeIcon>();
        byte[] icon;
        
        icon = getImageIcon(getPropertyTypeIcon(PropertyType.UNDEFINED).getInput());
        if (result != null)
            result.add(new PropertyTypeIcon(PropertyType.UNDEFINED, icon));

        icon = getImageIcon(getPropertyTypeIcon(PropertyType.STRING).getInput());
        if (result != null)
            result.add(new PropertyTypeIcon(PropertyType.STRING, icon));

        icon = getImageIcon(getPropertyTypeIcon(PropertyType.BINARY).getInput()); 
        if (result != null)
            result.add(new PropertyTypeIcon(PropertyType.BINARY, icon));

        icon = getImageIcon(getPropertyTypeIcon(PropertyType.LONG).getInput());
        if (result != null)
            result.add(new PropertyTypeIcon(PropertyType.LONG, icon));

        icon = getImageIcon(getPropertyTypeIcon(PropertyType.DOUBLE).getInput());
        if (result != null)
            result.add(new PropertyTypeIcon(PropertyType.DOUBLE, icon));
       icon = getImageIcon(getPropertyTypeIcon(PropertyType.DATE).getInput());
        if (result != null)
            result.add(new PropertyTypeIcon(PropertyType.DATE, icon));

        icon = getImageIcon(getPropertyTypeIcon(PropertyType.DATE).getInput());
        if (result != null)
            result.add(new PropertyTypeIcon(PropertyType.DATE, icon));

        icon = getImageIcon(getPropertyTypeIcon(PropertyType.BOOLEAN).getInput());
        if (result != null)
            result.add(new PropertyTypeIcon(PropertyType.BOOLEAN, icon));

        icon = getImageIcon(getPropertyTypeIcon(PropertyType.NAME).getInput());
        if (result != null)
            result.add(new PropertyTypeIcon(PropertyType. NAME, icon));

        icon = getImageIcon(getPropertyTypeIcon(PropertyType.PATH).getInput());
        if (result != null)
            result.add(new PropertyTypeIcon(PropertyType.PATH, icon));

        icon = getImageIcon(getPropertyTypeIcon(PropertyType.REFERENCE).getInput());
        if (result != null)
            result.add(new PropertyTypeIcon(PropertyType.REFERENCE, icon));

        icon = getImageIcon(getPropertyTypeIcon(-1).getInput());
        if (result != null)
            result.add(new PropertyTypeIcon(-1, icon));
        
        return result;
    }
    
    /**
     * Create an image icon from the node and return it.
     * 
     * @param file the nodes:file node
     * @return the image icon
     * @throws RepositoryException
     * @throws IOException 
     */
    private byte[] getImageIcon(Node file) throws RepositoryException, IOException {
        if (!file.hasNode(Constants.JCR_CONTENT))
            return null;

        Node content = file.getNode(Constants.JCR_CONTENT);
        if (!content.hasProperty(Constants.JCR_DATA))
            return null;
        
        InputStream input = content.getProperty(Constants.JCR_DATA).getStream();
        return getImageIcon(input);
    }
    
    /**
     * Return a byte array from an input stream
     * 
     * @param input the input stream
     * @return the byte array
     * @throws IOException 
     */
    public byte[] getImageIcon(InputStream input) throws IOException {
        byte[] imageBuf = null;
        try {
            imageBuf = new byte[input.available()];
            input.read(imageBuf);
        } finally {
            input.close();
        }

        return imageBuf;
        
    }

    /**
     * Return the node type icon. If there is no specific icon for the particular node type,
     * return the default icon.
     *
     * @param nodeTypeName
     * @return the icon, as a DownloadInfo
     * @throws RepositoryException
     */
    public BinaryAttribute getBooleanValueIcon(String propertyValue) throws RepositoryException {
        Node root = getSession().getRootNode();
        Node icon = null;

        String booleanValuePath = getIconDir() + Constants.PATH_SEPARATOR + "BOOLEAN" + Constants.PATH_SEPARATOR + propertyValue;
        if (!root.hasNode(booleanValuePath))
            return null;

        icon = root.getNode(booleanValuePath);
        return getDownloadInfo(icon);
    }

    /**
     * Return an icon associated to the property type of the specific property of the specific node type.
     *
     * @param nodeTypeName
     * @param propertyName
     * @return the icon, as a DownloadInfo or null
     * @throws RepositoryException
     */
    public BinaryAttribute getPropertyTypeIcon(String nodeTypeName, String propertyName) throws RepositoryException {
        SessionImpl s = (SessionImpl) getSession();
        NodeTypeManagerImpl nmgr = s.getNodeTypeManager();
        ServerNamespaceRegistry namespaces = new ServerNamespaceRegistry();
        Name nodeTypeQName = namespaces.toName(nodeTypeName);
        NodeTypeDef nodeTypeDef;
        try {
            nodeTypeDef = nmgr.getNodeTypeRegistry().getNodeTypeDef(nodeTypeQName);
        } catch (NoSuchNodeTypeException ex) {
            Logger.getLogger(IconModel.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }

        Name propertyQName = namespaces.toName(propertyName);
        PropDef[] propertyDefs = nodeTypeDef.getPropertyDefs();
        int propertyType = -1;
        for (PropDef propertyDef : propertyDefs) {
            if (propertyDef.getName().equals(propertyQName)) {
                propertyType = propertyDef.getRequiredType();
                break;
            }
        }

        return getPropertyTypeIcon(propertyType);
    }

    /**
     * Return an icon based on the given property type. This will either be
     * an icon specific for the property type or the property default type
     * icon.
     * 
     * @param propertyType
     * @return
     * @throws RepositoryException
     */
    protected BinaryAttribute getPropertyTypeIcon(int propertyType) throws RepositoryException {
        String[] propertyTypeNames = new String[]{
            "UNDEFINED",
            "STRING",
            "BINARY",
            "LONG",
            "DOUBLE",
            "DATE",
            "BOOLEAN",
            "NAME",
            "PATH",
            "REFERENCE"
        };

        String propertyTypeName;
        if (propertyType < 0)
            propertyTypeName = Constants.CHILD_DEFAULT_PROPERTY_TYPE;
        else
            propertyTypeName = propertyTypeNames[propertyType];

        Node root = getSession().getRootNode();

        String propertyTypeNodePath = getIconDir() + Constants.PATH_SEPARATOR + propertyTypeName;
        if (root.hasNode(propertyTypeNodePath)) {
            Node propertyTypeNode = root.getNode(propertyTypeNodePath);
            if (propertyTypeNode.isNodeType(Constants.JCR_UNSTRUCTURED)) {
                if (propertyTypeNode.hasNode(Constants.CHILD_ICON))
                    return getDownloadInfo(propertyTypeNode.getNode(Constants.CHILD_ICON));
            } else
                return getDownloadInfo(root.getNode(propertyTypeNodePath));
        }

        propertyTypeNodePath = getIconDir() + Constants.PATH_SEPARATOR + Constants.CHILD_DEFAULT_PROPERTY_TYPE;
        if (!root.hasNode(propertyTypeNodePath))
            return null;

        return getDownloadInfo(root.getNode(propertyTypeNodePath));
    }

    /**
     * Return the constant icon directory path
     * 
     * @return the path to the icon directory of nodes
     */
    protected String getIconDir() {
        return Constants.DIR_NODES + Constants.PATH_SEPARATOR + Constants.CHILD_ICONS;
    }
}
